// implementation of the public C API
#include <stdio.h>
#include <string.h>
#include <dirent.h>

#ifndef WIN32
#include <alloca.h>
#else
#include <malloc.h>
#endif

#include "goatvfs.h"
#include "vfs.h"

#undef GOATVFSAPI
#ifdef WIN32
#define GOATVFSAPI	__declspec(dllexport) extern "C"
#else
#define GOATVFSAPI	extern "C"
#endif

struct gvfs_file {
	VNodeFile *vnode;
	FILE *real;
};

struct gvfs_dir {
	VNodeDir *vnode;
	DIR *real;
};

GOATVFSAPI int gvfs_mount(const char *mntpoint, const char *target)
{
	VNode *node = lookup_path(mntpoint);
	if(!node || !node->is_dir()) {
		return -1;
	}

	VNodeDir *mdir = (VNodeDir*)node;
	if(!mdir->mount(target)) {
		return -1;
	}
	return 0;
}

GOATVFSAPI int gvfs_umount(const char *mntpoint)
{
	VNode *node = lookup_path(mntpoint);
	if(!node || !node->is_dir()) {
		return -1;
	}

	VNodeDir *mdir = (VNodeDir*)node;
	return mdir->umount() ? 0 : -1;
}

GOATVFSAPI int gvfs_chdir(const char *path)
{
	return change_dir(path) ? 0 : -1;
}

GOATVFSAPI int gvfs_mkdir(const char *path, unsigned int mode)
{
	return make_dir(path) ? 0 : -1;
}

GOATVFSAPI GVFS_FILE *gvfs_fopen(const char *fname, const char *mode)
{
	VNode *vnode = lookup_path(fname);
	if(vnode->is_real()) {
		if(!vnode->is_file()) {
			return nullptr;	// not a file...
		}
		// if it's a real file fallthrough to the standard I/O
		FILE *fp = fopen(fname, mode);
		if(!fp) {
			return nullptr;
		}

		GVFS_FILE *vfp = new GVFS_FILE;
		vfp->real = fp;
		vfp->vnode = nullptr;
		return vfp;
	}

	return nullptr;	// TODO implement the rest
}

GOATVFSAPI int gvfs_fclose(GVFS_FILE *fp)
{
	if(fp->real) {
		fclose(fp->real);
		fp->real = nullptr;
		return 0;
	}
	return -1;	// TODO
}

GOATVFSAPI int gvfs_fseek(GVFS_FILE *fp, long offset, int whence)
{
	if(fp->real) {
		return fseek(fp->real, offset, whence);
	}
	return -1;	// TODO
}

GOATVFSAPI long gvfs_ftell(GVFS_FILE *fp)
{
	if(fp->real) {
		return ftell(fp->real);
	}
	return -1;	// TODO
}

GOATVFSAPI void gvfs_rewind(GVFS_FILE *fp)
{
	gvfs_fseek(fp, 0, SEEK_SET);
}

GOATVFSAPI size_t gvfs_fread(void *buf, size_t size, size_t nitems, GVFS_FILE *fp)
{
	if(fp->real) {
		return fread(buf, size, nitems, fp->real);
	}
	return -1;	// TODO
}

GOATVFSAPI size_t gvfs_fwrite(void *buf, size_t size, size_t nitems, GVFS_FILE *fp)
{
	if(fp->real) {
		return fwrite(buf, size, nitems, fp->real);
	}
	return -1;	// TODO
}

GOATVFSAPI int gvfs_fgetc(GVFS_FILE *fp)
{
	char c;
	if(gvfs_fread(&c, 1, 1, fp) < 1) {
		return -1;
	}
	return c;
}

GOATVFSAPI int gvfs_fputc(int c, GVFS_FILE *fp)
{
	if(gvfs_fwrite(&c, 1, 1, fp) < 1) {
		return -1;
	}
	return 0;
}

GOATVFSAPI char *gvfs_fgets(char *buf, int size, GVFS_FILE *fp)
{
	int c;
	char *ptr = buf;

	while((c = gvfs_fgetc(fp)) >= 0 && --size > 0) {
		*ptr++ = c;
		if(c == '\n') break;
	}
	*ptr = 0;
	return buf;
}

GOATVFSAPI int gvfs_fputs(char *buf, GVFS_FILE *fp)
{
	size_t sz = strlen(buf);
	if(gvfs_fwrite(buf, 1, sz, fp) < sz) {
		return -1;
	}
	return 0;
}

/* TODO
GOATVFSAPI int gvfs_fscanf(GVFS_FILE *fp, const char *fmt, ...);
GOATVFSAPI int gvfs_vfscanf(GVFS_FILE *fp, const char *fmt, va_list ap);
GOATVFSAPI int gvfs_fprintf(GVFS_FILE *fp, const char *fmt, ...);
GOATVFSAPI int gvfs_vfprintf(GVFS_FILE *fp, const char *fmt, va_list ap);
*/

GOATVFSAPI void gvfs_clearerr(GVFS_FILE *fp)
{
	if(fp->real) {
		clearerr(fp->real);
		return;
	}
	// TODO
}

GOATVFSAPI int gvfs_feof(GVFS_FILE *fp)
{
	if(fp->real) {
		return feof(fp->real);
	}
	return -1;	// TODO
}

GOATVFSAPI int gvfs_ferror(GVFS_FILE *fp)
{
	if(fp->real) {
		return ferror(fp->real);
	}
	return -1;	// TODO
}

GOATVFSAPI GVFS_DIR *gvfs_opendir(const char *dirname)
{
	VNode *vnode = lookup_path(dirname);
	if(vnode->is_real()) {
		if(!vnode->is_dir()) {
			return nullptr;	// not a directory...
		}
		// if it's a real file fallthrough to the standard opendir
		DIR *dir = opendir(dirname);
		if(!dir) {
			return nullptr;
		}

		GVFS_DIR *vdir = new GVFS_DIR;
		vdir->real = dir;
		vdir->vnode = nullptr;
		return vdir;
	}

	return nullptr;	// TODO
}

GOATVFSAPI int gvfs_closedir(GVFS_DIR *dir)
{
	if(dir->real) {
		return closedir(dir->real);
	}
	return -1;	// TODO
}

GOATVFSAPI struct gvfs_dirent *gvfs_readdir(GVFS_DIR *dir)
{
	static gvfs_dirent vdent;

	if(dir->real) {
		struct dirent *dent = readdir(dir->real);
		if(!dent) {
			return nullptr;
		}
		strncpy(vdent.d_name, dent->d_name, sizeof vdent.d_name - 1);
		vdent.d_name[sizeof vdent.d_name - 1] = 0;
		return &vdent;
	}
	return nullptr;	// TODO
}

GOATVFSAPI void gvfs_rewinddir(GVFS_DIR *dir)
{
	if(dir->real) {
		return rewinddir(dir->real);
	}
	// TODO
}

GOATVFSAPI int gvfs_stat(const char *path, struct gvfs_stat *buf)
{
	return -1;	// TODO
}
